/*
 * matching_main.cpp
 *
 *  Created on: Apr 30, 2011
 *      Author: tqlong
 */

#include <boost/program_options.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "common/common.h"
#include "data/dataset.h"
#include "tree/kdtree.h"
#include "tree/tree_visitors.h"
#include "matcher/stable_matcher.h"
#include "matcher/auction_matcher.h"

namespace po = boost::program_options;
po::options_description desc("Allowed options");
po::variables_map vm;

std::string in, query, out, prob, format, norm_str;
int K;
double R;

void process_options(int argc, char** argv);
int doJob();

void usage()
{
  std::cout << desc << "\n";
  exit(0);
}

int main(int argc, char** argv)
{
  process_options(argc, argv);
  if (vm.count("help"))
    usage();

  boost::posix_time::ptime time_start(boost::posix_time::microsec_clock::local_time());

  int retval = doJob();

  boost::posix_time::ptime time_end(boost::posix_time::microsec_clock::local_time());
  boost::posix_time::time_duration duration(time_end - time_start);
  std::cerr << "Duration = " << duration << "\n";
  return retval;
}

void process_options(int argc, char** argv)
{
  desc.add_options()
    ("help", "produce this usage")
    ("in", po::value<std::string>(&in)->default_value(""), "file contains input")
    ("q", po::value<std::string>(&query)->default_value(""), "query file")
    ("out", po::value<std::string>(&out)->default_value(""), "output file")
    ("prob", po::value<std::string>(&prob)->default_value(""), "problem: kdtree, knn, range, match, auction")
    ("k", po::value<int>(&K)->default_value(2), "(knn) number of nearest neighbors")
    ("r", po::value<double>(&R)->default_value(2), "(range) radius")
    ("norm", po::value<std::string>(&norm_str)->default_value("2"), "(range) norm")
    ("all", "(range) find all in range")
    ("format ", po::value<std::string>(&format)->default_value(""), "data file format")
    //("inputs", po::value< std::vector<std::string> >(), "input files" )
    ;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);
}

void doKDTree()
{
  if (in.empty()) usage();
  if (out.empty()) usage();

  Log::m_log << "problem: kdtree" << endl;
  InMemoryDataset data(in, format); data.reload();
  typedef KDTree<InMemoryDataset> tree_type;

  tree_type *root = new tree_type(data);
  root->buildTree();
  root->save(out);
  delete root;
}

void doKNN()
{
  if (in.empty()) usage();
  if (query.empty()) usage();
  if (out.empty()) usage();

  Log::m_log << "problem: knn" << endl;
  typedef InMemoryDataset dataset_type;
  typedef KDTree<dataset_type> tree_type;
  typedef KNNVisitor<tree_type> visitor_type;

  dataset_type data(in, format, false); data.reload();
  tree_type *root = new tree_type(data);
  root->buildTree();

  dataset_type queries(query, format); queries.reload();
  std::ofstream os(out.c_str());

  for (int i = 0; i < queries.n(); i++)
  {
    const dataset_type::vec_type& q = queries.get(i);
    visitor_type v(q, K);
    searchTree<tree_type, visitor_type>(root, v);

    // output original index of results
    os << v.m_idx.size();
//    Log::m_log << "K=" << K << " size=" << v.m_idx.size() << endl;
    for (unsigned int k = 0; k < v.m_idx.size(); k++)
      os << " " << root->origIdx(v.m_idx[k]);
    os << std::endl;
  }

  os.close();
  delete root;
}

void doRange()
{
  bool all = vm.count("all");
  Log::m_log << "str norm = " << QString::fromStdString(norm_str) << endl;
  double norm = (norm_str == "inf") ? POSITIVE_INFINITY : atof(norm_str.c_str());

  if (in.empty()) usage();
  if (query.empty()) usage();
  if (out.empty()) usage();

  Log::m_log << "problem: range" << endl;
  typedef InMemoryDataset dataset_type;
  typedef KDTree<dataset_type> tree_type;
  typedef RangeVisitor<tree_type> visitor_type;

  dataset_type data(in, format); data.reload();
  tree_type *root = new tree_type(data);
  root->buildTree();

  dataset_type queries(query, format); queries.reload();
  std::ofstream os(out.c_str());

  for (int i = 0; i < queries.n(); i++)
  {
    const dataset_type::vec_type& q = queries.get(i);
    visitor_type v(q, R, all, norm);
    searchTree<tree_type, visitor_type>(root, v);

    // output original index of results
    os << v.m_idx.size();
    for (unsigned int k = 0; k < v.m_idx.size(); k++)
      os << " " << root->origIdx(v.m_idx[k]);
    os << std::endl;
  }

  os.close();
  delete root;
}

void doMatch()
{
  bool all = true;
  Log::m_log << "str norm = " << QString::fromStdString(norm_str) << endl;
  double norm = (norm_str == "inf") ? POSITIVE_INFINITY : atof(norm_str.c_str());

  if (in.empty()) usage();
  if (query.empty()) usage();
  if (out.empty()) usage();

  Log::m_log << "problem: range" << endl;
  typedef InMemoryDataset dataset_type;
  typedef StableMatcher<dataset_type, dataset_type> matcher_type;

  dataset_type data(in, format); data.reload();
  dataset_type queries(query, format); queries.reload();

  Log::m_log << "data.n=" << data.n() << " queries.n=" << queries.n() << endl;

  matcher_type matcher(data, queries, R, norm);
  matcher.doMatch();

  std::ofstream os(out.c_str());
  for (int i = 0; i < queries.n(); i++)
  {
    int j = matcher.match2(i);
    if (j == -1)
      os << 0 << std::endl;
    else
      os << 1 << " " << j << std::endl;
  }
  os.close();
}

void doAuction()
{
  bool all = true;
  Log::m_log << "str norm = " << QString::fromStdString(norm_str) << endl;
  double norm = (norm_str == "inf") ? POSITIVE_INFINITY : atof(norm_str.c_str());

  if (in.empty()) usage();
  if (query.empty()) usage();
  if (out.empty()) usage();

  Log::m_log << "problem: range" << endl;
  typedef InMemoryDataset dataset_type;
  typedef AuctionMatcher<dataset_type, dataset_type> matcher_type;

  dataset_type data(in, format); data.reload();
  dataset_type queries(query, format); queries.reload();

  Log::m_log << "data.n=" << data.n() << " queries.n=" << queries.n() << endl;

  matcher_type matcher(data, queries, R, norm);
  matcher.doMatch();

  std::ofstream os(out.c_str());
  for (int i = 0; i < queries.n(); i++)
  {
    int j = matcher.match2(i);
    if (j == -1)
      os << 0 << std::endl;
    else
      os << 1 << " " << j << std::endl;
  }
  os.close();
}

int doJob()
{
  std::string prob = vm["prob"].as<std::string>( );
//  std::vector<std::string> inputs = vm["inputs"].as<typeof(inputs)>();
//  for (unsigned int i = 0; i < inputs.size(); i++)
//    Log::m_log << "input[" << i << "]=" << QString::fromStdString(inputs[i]) << endl;

  if (prob == "kdtree")
    doKDTree();
  else if (prob == "knn")
    doKNN();
  else if (prob == "range")
    doRange();
  else if (prob == "match")
    doMatch();
  else if (prob == "auction")
    doAuction();
  else {
    Log::m_log << "specify a problem" << endl << endl;
    usage();
  }

  return 0;
}
